package service

import (
	"github.com/kordar/k-means-server/src/pojo"
	"github.com/kordar/k-means-server/src/util"
	"log"
)

type DbScan struct {
	Data       []pojo.FeatureItem
	FeatureMap map[string][]float64
	MinPts     int                // 领域密度阈值
	Eps        float64            // 半径
	InitIndex  int                // 初始核心点
	visited    map[int]int        // 已标记，样本id, 簇id，-1为噪声
	NoisePoint []pojo.FeatureItem // 噪声点
	Groups     map[pojo.FeatureItem][]pojo.FeatureItem
	ToMap      map[int]*pojo.FeatureItem // 样本所属分组map
}

// 邻域([公式]-neighborhood）
func (k *DbScan) neighborhood(corePoint pojo.FeatureItem) []pojo.FeatureItem {
	data := make([]pojo.FeatureItem, 0, k.MinPts)
	for _, datum := range k.Data {
		if datum.Id == corePoint.Id {
			continue
		}
		cosine := util.Cosine(k.FeatureMap[corePoint.Url], k.FeatureMap[datum.Url])
		if (1 - cosine) <= k.Eps {
			data = append(data, datum)
		}
	}
	return data
}

func (k *DbScan) Run() map[pojo.FeatureItem][]pojo.FeatureItem {
	k.visited = make(map[int]int, len(k.Data))
	k.Groups = make(map[pojo.FeatureItem][]pojo.FeatureItem)
	k.NoisePoint = make([]pojo.FeatureItem, 0, 10)
	k.ToMap = make(map[int]*pojo.FeatureItem)
	/*corePoint := k.Data[k.InitIndex]
	k.execute(corePoint)*/
	for _, datum := range k.Data {
		if k.visited[datum.Id] == 0 {
			k.execute(datum)
		}
	}

	log.Println("噪声点：", k.NoisePoint)
	return k.Groups
}

func (k *DbScan) execute(corePoint pojo.FeatureItem) {
	neighborhood := k.neighborhood(corePoint) // 获取领域
	if len(neighborhood) < k.MinPts {
		// p 领域少于最小阈值，标记为噪声
		k.visited[corePoint.Id] = -1
		k.NoisePoint = append(k.NoisePoint, corePoint)
		k.ToMap[corePoint.Id] = nil // nil 为噪声
	} else {
		// 创建新簇C，将corePoint添加到C中
		k.Groups[corePoint] = make([]pojo.FeatureItem, 0)
		k.Groups[corePoint] = append(k.Groups[corePoint], corePoint)
		k.ToMap[corePoint.Id] = &corePoint //
		k.visited[corePoint.Id] = corePoint.Id
		// group := k.groups[corePoint]
		// 循环领域中的数据
		nei := neighborhood
		for _, item := range nei {
			if k.visited[item.Id] == 0 {
				// 样本unvisited
				neighborhood2 := k.neighborhood(item)
				for _, item2 := range neighborhood2 {
					neighborhood = append(neighborhood, item2)
				}
				k.visited[item.Id] = corePoint.Id
			}
		}

		for _, item := range neighborhood {
			if k.ToMap[item.Id] == nil {
				k.Groups[corePoint] = append(k.Groups[corePoint], item)
				k.ToMap[item.Id] = &corePoint //
			}
		}

	}
}
